Skip to content

Fix narrowing to non-falsy-string when '' and '0' equality checks are in compound conditions#5688

Open
phpstan-bot wants to merge 1 commit into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-r91isvg
Open

Fix narrowing to non-falsy-string when '' and '0' equality checks are in compound conditions#5688
phpstan-bot wants to merge 1 commit into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-r91isvg

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

Fixes phpstan/phpstan#9114

When '' and '0' equality checks appear in a compound boolean condition (|| or &&), PHPStan failed to narrow the variable to non-falsy-string. This happened because removing '0' from string is "lossy" (cannot be represented in the type system), so by the time '' removal narrowed the type to non-empty-string, the '0' removal information was already lost.

Changes

  • TypeCombinator::remove(): When removing a union type, retry members that failed in a loop until no more progress is made. This handles the case where removing ''|'0' from string now correctly produces non-falsy-string regardless of member ordering.
  • BooleanAndHandler: After computing the truthy scope, re-apply left arm's sureNotType removals that were previously lossy but can now succeed on the narrower type from the right arm.
  • BooleanOrHandler: Same logic for the falsey scope computation.
  • Both handlers include guards to skip re-application when: (1) the expression type was modified between left and right arms (e.g., by an impure function call or variable reassignment), or (2) the removal would be a no-op on the current type.

Test cases

All variants from the issue are covered:

  • '' === $foo || '0' === $foo followed by throw → non-falsy-string
  • '0' === $foo || '' === $foo followed by throw → non-falsy-string
  • '' !== $foo && '0' !== $foonon-falsy-string in truthy branch
  • '0' !== $foo && '' !== $foonon-falsy-string in truthy branch

Test plan

  • New regression test tests/PHPStan/Analyser/nsrt/bug-9114.php passes
  • Full test suite passes (12078 tests, 79704 assertions)
  • make phpstan self-analysis passes with no errors
  • No regressions in previously-failing edge cases (bug-9400, bug-7156, bug-7699, bug-6702, bug-9503)

When checking '' and '0' in a single || or && expression, the type
narrowing to non-falsy-string was lost because the sequential removal
of constant string types from the scope was lossy — removing '0' from
'string' cannot be represented, so the removal information was lost
before '' could be removed (which would have enabled the '0' removal
to succeed on the narrower non-empty-string type).

Two fixes:
1. TypeCombinator::remove() now retries union type member removals in
   a loop, so that removing ''|'0' from string correctly yields
   non-falsy-string regardless of member ordering.
2. BooleanAndHandler/BooleanOrHandler now re-apply "lossy" sureNotType
   removals from the left arm after the right arm's narrowing succeeds,
   enabling the sequential per-arm path to also produce non-falsy-string.

Closes phpstan/phpstan#9114
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants